package com.ywk.base.concurrency.thread.method;

import lombok.SneakyThrows;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * 线程的五种状态：初始化，就绪，运行，阻塞，终止
 *
 * 补充：
 * 进程、线程、管程区别：
 * 进程：系统中运行的一个应用程序就是一个进程，每一个进程都有它自己的内存空间和系统资源。
 * 线程：也被称为轻量级进程，在同一个进程内基本会有1一个或多个线程，是大多数操作系统进行调度的基本单元。
 * 管程：
 * - Monitor（监视器），也就是我们平时说的锁
 * - Monitor其实是一种同步机制，他的义务是保证（同一时间）只有一个线程可以访问被保护的数据和代码。
 * - JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的，每个对象实例都会有一个Monitor对象，
 *
 * 线程有两种调度模型：
 * 1.分时调度模式：所有线程轮流使用CPU的使用权,平均分配每个线程占有CPU的时间片
 * 2.抢占式调度模型：优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些 [ Java使用的是抢占式调度模型 ]
 *
 * @Author zbh
 * @Date 2024/1/2
 */
public class ThreadMethodTest {
    public static void main(String[] args) {
//        createWay();
//        sleepTest();
//        joinTest1();
//        joinTest2();
//        priorityTest();
//        yieldTest();
        daemonTest();
    }

    /**
     *
     * 多个线程同时在跑,都在获取cpu的使用权，cpu执行到谁，谁就执行，明确一点，在某一个时刻，只能有一个程序在运行（多核除外），
     * cpu在做着快速的切换以达到看上去是同时运行的效果，也就是说多线成的一个特性叫随机性，谁抢到谁执行，但是执行多长，cpu说了算
     */
    static void createWay(){
        //创建线程四种方式

        //方式一：Thread 核心类
        new Thread(){
            @SneakyThrows
            public void run(){
                System.out.println(Thread.currentThread().getName()+"-T");
            }
        }.start();

        //方式二：Runnable 没有返回值，也不能抛异常
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"-R");
            }
        }).start();

        //方式三：Callable<T> 允许有返回值，允许抛出异常。（Callable是JDK1.5时加入的接口，作为Runnable的一种补充）
        new Thread(new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"-C");
                return 6;
            }
        })).start();

        //方式四：线程池 Executors
        ExecutorService threadPool= Executors.newFixedThreadPool(3);//创建含有3个线程的线程池
        for (int i = 1; i <= 5; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "-E");
                }
            });
        }
        System.out.println(Thread.currentThread().getName());
    }

    /**
     * sleep()将当前线程处于阻塞状态（如果有锁是不会释放锁的），让出 cpu 给其他线程，
     *
     * 补充：sleep()和wait()区别
     * 1.sleep() 属于 Thread 类的，而 wait() 是属于Object 类中的。
     * 2.调用 sleep() 方法时，线程不会释放对象锁，而调用 wait() 方法时，线程会释放对象锁
     * 关键点：都会让出cpu，都是Java中造成线程阻塞的方法，但是sleep不会释放同步锁，wait会释放同步锁
     */
    static void sleepTest(){
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t");
        });
        t.start();
        System.out.println("main");
    }


    /**
     * join()阻塞当前线程,等待指定的线程执行结束后,当前线程再继续 (相当于插队加入)
     * 案例：现在有T1、T2、T3三个线程，保证T2在T1执行完后执行，T3在T2执行完后执行
     * 总结:Thread.join()相当于一个阻塞方法
     */
    static void joinTest1(){
        //初始化线程一
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t1 is running...");
            }
        });

        //初始化线程二
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    t1.join();//阻塞t2线程的执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("t2 is running...");
                }
            }
        });

        //初始化线程三
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    t2.join();//阻塞t3线程的执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("t3 is running...");
                }
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }

    /**
     * 输出结果有两种:
     * 1.main1 -> test -> main2
     * 2.test -> main1 -> main2
     *
     * 总结: main1 和 test 谁先后不一定,但 main2 一定是在 test 后面执行
     */
    static void joinTest2(){
        Thread t = new Thread(() -> System.out.println("test"));
        t.start();
        System.out.println("main1");
        try {
            t.join();//阻塞主线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main2");
    }

    /**
     * 线程优先级 setPriority()
     * 注：
     * 1. 线程默认优先级是5，线程优先级范围是:1-10；
     * 2. 线程优先级高仅仅表示线程获取的CPU时间的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
     */
    static void priorityTest(){
        Thread t1 = new Thread(() -> System.out.println("T1"));
        t1.setPriority(3);

        Thread t2 = new Thread(() -> System.out.println("T2"));
        t2.setPriority(10);

        t1.start();
        t2.start();
    }

    /**
     * yield()让出cpu的执行权(礼让线程)，不改变线程对锁的持有状态，将当前线程从运行态变为就绪态,从而使其他具有相同优先级的更高等级的线程能够有机会运行
     * 注：
     * 由于操作系统内核采用的调度策略可能有所不同。则造成yield()方法的应用在不同的平台、环境、压力下的表现也有所差异。
     * 它与interrupt()方法一样，都不能保证打断或挂起当前线程的执行方式。所以，yield()方法也应该谨慎使用
     *
     */
    static void yieldTest(){
        Thread t3 = new Thread(() -> {
            Thread.yield();
            System.out.println(Thread.currentThread().getName());
        },"T3");

        Thread t4 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
        },"T4");

        t3.start();
        t4.start();
    }

    /**
     * setDaemon()将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出(守护线程)
     * 注：setDaemon(true)一定要在start()方法之前使用
     *
     * 补充：线程分两种
     * 1.用户线程：是系统的工作线程，它会完成这个程序需要完成的业务操作。
     * 2.守护线程：是一种特殊的线程，为其他线程服务的，拥有自动结束自己生命周期的特性,非守护线程却没有，在后台默默地完成一些系统性的服务。比如垃圾回收。
     */
    @SneakyThrows
    static void daemonTest(){
        Thread t5 = new Thread(() -> {
            //设置为一直执行
            while (true){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName());
                } catch (Exception e) {
                }
            }
        },"T5");
        //标记为守护线程,主线程销毁，守护线程也销毁（即当运行的线程都是守护线程时,Java虚拟机将退出，运行自动结束）
        t5.setDaemon(true);
        t5.start();

        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName());
    }
}
